/******************************************************************************
*
* This file is part of FreeTrack (http://code.google.com/p/qtfreetrack).
* BY Willy Gardiol (willy@gardiol.org)
*
* FreeTrack is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* FreeTrack is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with Foobar.  If not, see <http://www.gnu.org/licenses/>
*
******************************************************************************/

#include "ftwidgetlist.h"
#include "ftpushbutton.h"
#include "ftwidgetlistitem.h"
#include "fticonlabel.h"
#include "ftmessagebox.h"

#define FTWL_MOVING_TIMEOUT 300
#define FTWL_MOVING_PEN QPen( QColor("red"), 4 )
#define FTWL_MOVING_BRUSH QBrush( QColor("red"), Qt::BDiagPattern )

#define FTWL_PLACEHOLDER_BRUSH QBrush(QColor("green"), Qt::FDiagPattern)
#define FTWL_PLACEHOLDER_PEN QPen( QColor("white"), 1)

FTWidgetList::FTWidgetList(QWidget *parent) :
    QWidget(parent)
{
    _autoSelect = true;
    allowMove = allowDelete = true;

    // This widget will always expand to all the available space
    setSizePolicy( QSizePolicy::Ignored, QSizePolicy::Ignored );

    // The "state machine" always start "atRest"
    state = atRest;

    // Initialize the drag&drop items
    drop_target = moving_widget = NULL;

    // We also create here the delete widget. We make it hidden, we show it only
    // when state == atMoving.
    delete_widget = new FTWidgetListItem(this, new FTIconLabel( QIcon(":/misc/delete.png")) );
    delete_widget->hide();

}

bool FTWidgetList::eventFilter(QObject* source, QEvent* event)
{
    // Store the vent type for multiple access
    QEvent::Type type = event->type();

    // It depends on our state:
    if ( state == atRest )
    {   // mousePress: processed, will start the state change
        // mouseMove: processed, will do the state change (but check for a "threeshold" to cope with shaky fingers)
        // mouseRelease: processed, will end the state change
        if ( type == QEvent::MouseButtonPress )
        {
            // TODO: this event shuold be saved and filtered. And resent after only if needed!

            // This position is the reference for the shaky finger test
            scroll_start = ((QMouseEvent*)event)->globalPos();
            // get ready for the move state
            state_change_timer.start( FTWL_MOVING_TIMEOUT, this );
            // Then store, for later reference, which item triggered the state change request
            moving_widget = getProperWidget(source);
        }
        else if ( type == QEvent::MouseMove ) // this works bcause on phone we receive move only after a press
        {
            // Calculate distance between this new point and the threeshold starting point
            QPoint shaky_distance = ((QMouseEvent*)event)->globalPos() - scroll_start;
            // do the state change if the finger moved more than our threeshold
            if ( shaky_distance.manhattanLength() > FTControls::shakyThreeshold() )
                performScrolling();
        }
        else if ( type == QEvent::MouseButtonRelease )
        {
            // A mouse release will always reset the state machine
            resetStateMachine();
        }
    }
    else // all other states are simple: just propagate the event to us and dont send it to 'source'.
    {   // mouseRelease & mousePress & mouseMove: propagated to parent, not processed
        if ( (type == QEvent::MouseButtonRelease) ||
             (type == QEvent::MouseButtonPress) ||
             (type == QEvent::MouseMove) )
            event->ignore();
    }
    // We always propagate the event
    return false;
}

void FTWidgetList::timerEvent(QTimerEvent *)
{
    // First, stop the timer
    state_change_timer.stop();
    // To go to the atMoving state we also need to have a moving_widget stored
    // otherwise, just ignore it.
    if ( (allowMove||allowDelete) && (state == atRest) && (moving_widget != NULL) )
    {
        state = atMoving;
        // The atMoving state is implemented trough drag&drop Qt support
        performDragAndDrop();
    }
}

void FTWidgetList::setAutoSelect(bool a)
{
    _autoSelect = a;
}

bool FTWidgetList::autoSelect() const
{
    return _autoSelect;
}

void FTWidgetList::performScrolling()
{
    if ( state == atRest )
    {
        // First, stop the timer
        state_change_timer.stop();
        // from atRest we go to the atScrolling state:
        state = atScrolling;
    }
}

void FTWidgetList::resizeEvent(QResizeEvent *)
{
    // This code is very important for proper children placement.
    // Every child will be resized horizontally to fit the entire list width
    // and vertically, each widget is moved just below the previous one.

    // This is the Y to place the next widget
    int topPlacement = -1;
    // Quick access for width of this widget (the list)
    int listWidth = width();
    foreach (QWidget* item, items_order )
    {
        QRect itemRect = item->geometry();
        // Initialize topPlacement with the first item top position
        if ( topPlacement == -1 )
            topPlacement = itemRect.top();
        // Set child item size:
        itemRect.setWidth(listWidth);
        // Move the item to its proper place:
        itemRect.moveTo(0, topPlacement);
        // Apply new position
        item->setGeometry( itemRect );
        // Move topPlacement to bottom of item
        topPlacement = itemRect.bottom();
    }
    // Now, place the delete widget in the top-left corner
    delete_widget->move( 0,0 );
}

void FTWidgetList::paintEvent(QPaintEvent *)
{
    QPainter painter(this);
    // atMoving is the only state requiring dedicated painting
    // The second test is probably now redoundant but i like to keep it just for safety.
    if ( (state == atMoving) && (moving_widget != NULL) )
    {
        // draw something to mark which is the moving item
        painter.fillRect( moving_widget->geometry(), FTWL_MOVING_BRUSH);

        // We set a special pen for atMoving
        painter.setPen( FTWL_MOVING_PEN );

        // If we have an active drop_targer
        if (drop_target != NULL)
        {
            QRect drop_rect = drop_target->geometry();
            // delete widget will have a rect around it, other items only a lin on top
            if ( allowDelete && (drop_target == delete_widget) )
                painter.drawRect( drop_rect );
            else if ( allowMove )
                painter.drawLine( drop_rect.topLeft(),
                                  QPoint( drop_rect.right(), drop_rect.top() ) );
        }
    }
    // Draw a border around the list
    painter.drawRect( rect().adjusted(1,1,-1,-1) );

    int item_y;
    QPen pen = painter.pen();
    pen.setStyle( Qt::DotLine );
    painter.setPen( pen );
    foreach (FTWidgetListItem* item, items_order )
    {
        item_y = item->y();
        painter.drawLine( 0, item_y, width(), item_y );
    }
}

void FTWidgetList::mouseMoveEvent(QMouseEvent * me)
{
    // We only react when in atScrolling.
    if ( state == atScrolling )
    {
        QPoint current_pos = me->globalPos();
        // Abort state transition to atMoving when we move above the threeshold
        if ( state_change_timer.isActive() &&
             ((current_pos - scroll_start).manhattanLength() > FTControls::shakyThreeshold()) )
            state_change_timer.stop();
        else
        {   // Otherwise, just move the items up or down
            int delta = (scroll_start - current_pos).y();
            // Again, this test should be reodundant because we cannot be atScrolling if we do not
            // have at least one item to receive the first mousePRess event, but i like to be safe!
            if ( items_order.size() > 0 )
            {
                // Get the first and the last item
                QRect first_geometry = items_order.first()->geometry();
                QRect last_geometry = items_order.last()->geometry();
                // Move the items only if the first is not goint to be below the bottom edge of the screen
                // or the last is not going to be above the top edge of the screen...
                // TODO: this leaves out the case where the widget is smaller than one
                // item. In this case, scrolling will be disabled. Widget should set minimumHeight
                // to avoid this. TODO.
                if ( (last_geometry.top() > (delta)) && // should be top-delta > 0
                     (first_geometry.bottom() < (height()+delta)) ) // should be top-delta < height
                    foreach (QWidget* item, items_order )
                        item->move( 0,  item->y() - delta );
            }
            update();
            scroll_start = current_pos;
        }
    }
    else if ( state == atRest )
    {
        // Calculate distance between this new point and the threeshold starting point
        QPoint shaky_distance = me->globalPos() - scroll_start;
        // do the state change if the finger moved more than our threeshold
        if ( shaky_distance.manhattanLength() > FTControls::shakyThreeshold() )
            performScrolling();
    }
}

void FTWidgetList::mousePressEvent(QMouseEvent * me)
{
    if ( state == atRest )
    {
        // This position is the reference for the shaky finger test
        scroll_start = me->globalPos();
        // get ready for the move state
        state_change_timer.start( FTWL_MOVING_TIMEOUT, this );
        // Then store, for later reference, which item triggered the state change request
    }
}

void FTWidgetList::mouseReleaseEvent(QMouseEvent* me)
{
    if ( _autoSelect && (state == atRest) )
    {
        int mouse_y = me->pos().y();
        int item_y;
        foreach (FTWidgetListItem* item, items_order )
        {
            item_y = item->y() + item->height();
            if ( item_y > mouse_y )
            {
                if ( items.contains( item ) )
                    emit selectedItem( items.value( item) );
                break;
            }
        }
    }
    // Any mouse release will always reset the state machine
    resetStateMachine();
}


FTWidgetListItem* FTWidgetList::getProperWidget(const QObject* source )
{
    // This is required to "climb up" the widget hierarchy until we get to the proper child item
    foreach ( FTWidgetListItem* item, items_order )
        if ( ((QWidget*)item->getItem()) == source )
            return item;
    return NULL;
}

void FTWidgetList::resetStateMachine()
{
    // Stop the state timer, if started
    if ( state_change_timer.isActive() )
        state_change_timer.stop();
    // Reset our internal references to drag&drop items
    moving_widget = drop_target = NULL;
    // go back to the first state.
    state = atRest;
}

void FTWidgetList::performDragAndDrop()
{
    // Proceed only if atMoving and if we have a proper moving widget
    if ( !((state == atMoving) && (moving_widget != NULL)) )
        return;

    // Initialize QDrag object & mime data
    QDrag *drag = new QDrag(this);
    QMimeData *mimeData = new QMimeData;
    mimeData->setText( moving_widget->getId() );
    drag->setMimeData(mimeData);

    // Build the QPixmap to use as "placeholder" under the mouse cursor:
    QImage placeholder_image( moving_widget->size()*6/10, QImage::Format_ARGB32 );
    placeholder_image.fill(0);
    QPainter painter(&placeholder_image);
    painter.fillRect( placeholder_image.rect(), FTWL_PLACEHOLDER_BRUSH );
    painter.setPen( FTWL_PLACEHOLDER_PEN );
    painter.drawRect( placeholder_image.rect().adjusted(0,0,-1,-1));
    // Set the image as placeholder
    drag->setPixmap( QPixmap::fromImage(placeholder_image) );

    // Show the delete widget
    delete_widget->raise();
    delete_widget->show();
    update();

    // reset drop_target
    drop_target = NULL;

    // Perform the drag and drop operation
    Qt::DropAction dropAction = drag->exec();

    // Now hide the delete_widget, because we do not need it anymore
    delete_widget->hide();

    // Now analyze the result...
    if ( (dropAction != Qt::IgnoreAction) && // skip ignore actions
         (drag->source() != drag->target()) && // skip drop on itself
         (drop_target != NULL) ) // skip if there is no drop_target set
   {
        // get source widget order position
        int source_pos = items_order.indexOf( moving_widget );

        // if the target is the delete widget, then we must delete the item...
        if ( allowDelete && (drop_target == delete_widget) )
        {   // Ask for confirmation...
            if ( FTMessageBox::Yesno(tr("Remove item..."),
                                     tr("Remove item?") ) )
            {
                // Get the key associated to the source widget
                int source_key = items.take( moving_widget );
                // remove and delete the source widget
                delete items_order.takeAt(source_pos);
                // Send the signal!
                emit itemDeleted( source_key );
                // Ensure the remaining items are visible (we might have deleted the only one visible):
                if ( items_order.size() > 0 )
                {
                    if ( (items_order.first()->geometry().top() > (height()*8/10)) ||
                         (items_order.last()->geometry().bottom() < 5) )
                    {
                        // just a quick fix: move the first item back to the top
                        QRect rect = items_order.first()->geometry();
                        rect.moveTo( QPoint(0, 0 ) );
                        items_order.first()->setGeometry( rect );
                    }
                }
            }
        }
        else if ( allowMove )// Else, we are dropping the source widget just before the target widget
        {
            int destination_pos = items_order.indexOf( drop_target );
            if ( destination_pos == 0 )
                // This is a small hack to prevent the topmost widget to "slide down"
                // when we swap it with another one
                moving_widget->move( drop_target->pos() );

            // Swap the order of the two widgets
            items_order.swap( source_pos,destination_pos );
            // Rebuild the sorted list of the widget's keys
            QList<int> sorted_key_list;
            foreach (FTWidgetListItem* item, items_order )
                sorted_key_list.append( items.value(item) );
            // emit the items sorted signal
            emit orderChanged( sorted_key_list );
        }
        // Now force a resize, because this will reposition all the child widgets properly:
        resizeEvent(NULL);
    }
    // Go back to atRest state
    resetStateMachine();
    update();
}

void FTWidgetList::dragHere(FTWidgetListItem * item)
{
    drop_target = item;
    update();
}

void FTWidgetList::appendWidget(QWidget* widget)
{
    static int item_key = 999999;
    appendWidget( item_key--, widget );
}

void FTWidgetList::appendWidget(int item_key,  QWidget* widget)
{
    // Create the child item:
    FTWidgetListItem* new_item = new FTWidgetListItem(this, widget);
    // Append the item in the storage
    items.insert(new_item, item_key );
    items_order.append( new_item );
    // Reparenting a widget will make it hidden, so lets make sure its visible (probably redoundant here)
    new_item->show();
    // Force a resize of the entire widget, its a trick to fix the size of the new child
    resizeEvent(NULL);
}

void FTWidgetList::removeWidget(int item_key )
{
    FTWidgetListItem* item;
    if ( (item = items.key( item_key, NULL ) ) != NULL )
    {
        items_order.takeAt( items_order.indexOf( item ) );
        items.take( item );
        delete item;
        resizeEvent(NULL);
        update();
    }
}

void FTWidgetList::setActions(bool move, bool del)
{
    allowMove = move;
    allowDelete = del;
}
